package controllers;

import java.util.LinkedList;
import java.util.List;

import models.ApplicationRole;
import models.Project;
import models.UserStory;
import play.data.validation.Required;
import play.mvc.Before;
import play.mvc.Controller;
import play.mvc.With;

/**
 * 
 * The controller class for the ApplicationRoles. It connects the views with the
 * Models, and controls the interactions between them.
 * 
 */
@With(Secure.class)
public class ApplicationRoles extends Controller {

	/**
	 * Redirects to the form for creating a new Application Role, given the
	 * projectId and the storyId of the User Story that the Application Role
	 * will be associated to.
	 * 
	 * @param projectId
	 *            The id of the project that the role will be associated to
	 * @param storyId
	 *            The id of the user story that the role will be associated to
	 * @param associate
	 *            Determines whether the role is to be associated with a story.
	 */
	public static void showCreateForm(final long projectId, final long storyId,
			final int associate) {
		Security.checkAccess(
				"Create/edit/delete application roles in my project ",
				projectId, Project.class.getName());
		Project project = (Project) Project.findById(projectId);
		render(projectId, storyId, associate, project);
	}

	/**
	 * Takes as input the roleName written in the create form and the storyId
	 * passed form the HTTP request, and calls the create method. If the method
	 * returns true, then a success message is displayed and it redirects back
	 * to the editing page of the user story in the case that associate = 1
	 * meaning that the created role is to be immediately associated to a story.
	 * If associate = 0, then the role is merely going to be created and then it
	 * will redirect to the page containing all the application roles in the
	 * project. If the return is false, then the user stays on the same page and
	 * an error message is displayed stating that the name is not unique within
	 * the project.
	 * 
	 * @param roleName
	 *            The name of the role that will be created
	 * @param storyId
	 *            The id of the user story that the role will be associated to
	 * @param associate
	 *            Determines whether the role is to be associated with a story.
	 * @param projectId
	 *            The id of the project.
	 */
	public static void submitCreateForm(final String roleName,
			final long storyId, final int associate, final long projectId) {
		Security.checkAccess(
				"Create/edit/delete application roles in my project ",
				projectId, Project.class.getName());
		UserStory story = null;
		if (associate == 1) {
			story = UserStory.findById(storyId);
		}
		if (create(roleName, story, associate, projectId)) {
			flash.success("Role \"%s\" has successfully " + "been added!",
					roleName);
			if (associate == 1) {
				UserStories.show(storyId);
			} else {
				if (associate == 0) {
					showAll(projectId);
				} else {
					UserStories.index(projectId);
				}
			}
		} else {
			flash.error("Error! Role name is already in use!");
			showCreateForm(projectId, storyId, associate);
		}
	}

	/**
	 * Connects with the ApplicationRole model to create a new application role
	 * and add it to the user story given, if any, given that the role name is
	 * unique within the project.
	 * 
	 * @param roleName
	 *            The name of the role that will be added
	 * @param story
	 *            The user story to which the role will be associated if any.
	 * @param associate
	 *            Whether it will be associated to a story or not. Takes the
	 *            value 0 or 1
	 * @param projectId
	 *            The id of the project to which this role is to be added
	 * @return boolean - returns true if the role has been successfully created,
	 *         returns false if the role cannot be created due to violation of
	 *         uniqueness constraints.
	 */
	public static boolean create(final String roleName, final UserStory story,
			final int associate, final long projectId) {
		Project project = Project.findById(projectId);
		boolean unique = checkUniqueName(roleName, projectId);
		if (unique) {
			ApplicationRole newAppRole = new ApplicationRole(roleName, project);
			newAppRole.save();
			newAppRole.project.appRoles.add(newAppRole);
			newAppRole.project.save();
			if (associate == 1) {
				story.appRoles.add(newAppRole);
				story.save();
				if (!newAppRole.userStories.contains(story)) {
					newAppRole.userStories.add(story);
				}
				newAppRole.save();
			}
			return true;
		}
		return false;
	}

	/**
	 * Gets the list of all Application Roles within a certain Project and
	 * renders the list to the view. If the project currently has no Application
	 * roles, An error message will be rendered to the view stating that the
	 * Project currently has no associated Application Roles.
	 * 
	 * @param projectId
	 *            the id of the project that the list of roles is displayed for
	 */
	public static void showAll(final long projectId) {
		List<ApplicationRole> allAppRoles = getAll(projectId);
		Project project = Project.findById(projectId);
		if (allAppRoles.isEmpty()) {
			flash.error("Project \"%s\" currently has"
					+ " no Application Roles", project.name);
		}
		render(allAppRoles, projectId, project);
	}

	/**
	 * Retrieves a list of all the distinct Application Roles within a certain
	 * Project given the projectId. Returns an empty list if there are no roles
	 * in the project.
	 * 
	 * @param projectId
	 *            The id of the project for which the roles will be retrieved
	 * @return List<ApplicationRole> The list of application roles in the
	 *         project
	 */
	public static List<ApplicationRole> getAll(final long projectId) {
		Project project = Project.findById(projectId);
		return project.appRoles;
	}

	/**
	 * Checks whether the Application Role name already exists within a certain
	 * project. Returns true if the roleName is unique within the Project.
	 * 
	 * @param roleName
	 *            The name that is to be checked for uniqueness constraints
	 * @param projectId
	 *            The id the project within which the name must be unique
	 * @return boolean - The return value indicates whether the name is unique
	 *         or not; true means the name is unique, false means that a role
	 *         with that name already exists within this project
	 */
	public static boolean checkUniqueName(final String roleName,
			final long projectId) {
		List<ApplicationRole> allAppRoles = getAll(projectId);
		boolean unique = true;
		for (int i = 0; i < allAppRoles.size(); i++) {
			if (allAppRoles.get(i).name.equals(roleName)) {
				unique = false;
				break;
			}
		}
		return unique;
	}

	/**
	 * Renders the Application Role with the given roleId in the Project with
	 * the given projectId to a form for editing the Application Role.
	 * 
	 * @param roleId
	 *            The id of the role that is to be edited.
	 * @param projectId
	 *            The id of the project that the role is associated to.
	 */
	public static void showEditForm(final long roleId, final long projectId) {
		Security.checkAccess(
				"Create/edit/delete application roles in my project ",
				projectId, Project.class.getName());
		ApplicationRole appRole = ApplicationRole.findById(roleId);
		Project project = Project.findById(projectId);
		render(appRole, projectId, project);
	}

	/**
	 * Takes the roleName written in the edit form and the storyId passed form
	 * the HTTP request, and calls the edit method. If the method returns true,
	 * then a success message is displayed and it redirects back to the list of
	 * Application Roles. If the return is false, then the user stays on the
	 * same page and an error message is displayed stating that the name is not
	 * unique within the project.
	 * 
	 * @param roleId
	 *            The id of the role that is to be updated.
	 * @param roleName
	 *            The new value for the name that will replace the old value
	 *            given that it is unique.
	 * @param projectId
	 *            The id of the project for which the value will be changed.
	 */
	public static void submitEditForm(final long roleId, final String roleName,
			final long projectId) {
		Security.checkAccess(
				"Create/edit/delete application roles in my project ",
				projectId, Project.class.getName());
		ApplicationRole appRoles = ApplicationRole.findById(roleId);
		if (edit(appRoles, roleName, projectId)
				|| roleName.equals(appRoles.name)) {
			showAll(projectId);
		} else {
			flash.error("Role \"%s\" is already in use!", roleName);
			showEditForm(roleId, projectId);
		}
	}

	/**
	 * Connects with the ApplicationRole model to edit the application role
	 * given that the role name is unique within the project.
	 * 
	 * @param appRole
	 *            The Application Role that will be edited.
	 * @param roleName
	 *            The new name that is to be given to the role.
	 * @param projectId
	 *            The id of the project.
	 * @return boolean - true if the role has been successfulyy edited, false if
	 *         the role name is not unique.
	 */
	public static boolean edit(final ApplicationRole appRole,
			final String roleName, final long projectId) {
		boolean unique = checkUniqueName(roleName, projectId);
		if (unique) {
			appRole.name = roleName;
			appRole.save();
			return true;
		} else {
			if (appRole.name.equals(roleName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Renders the view of all the Application Roles in a certain Project to
	 * select the Application Role that is to be associated with the User Story
	 * that has id storyId.
	 * 
	 * @param storyId
	 *            The id of the user story that the later on selected role will
	 *            be associated.
	 */
	public static void showAssociateRole(final long storyId) {
		Security
				.checkAccess(
						"Associate/disassociate application roles to/from a user story",
						storyId, UserStory.class.getName());
		UserStory story = UserStory.findById(storyId);
		long projectId = (Long) story.component.project.getEntityId();
		List<ApplicationRole> allAppRoles = getAll(projectId);
		for (int i = 0; i < allAppRoles.size(); i++) {
			if (story.appRoles.contains(allAppRoles.get(i))) {
				allAppRoles.remove(i);
				i--;
			}
		}
		Project project = Project.findById(projectId);
		render(allAppRoles, storyId, project);
	}

	/**
	 * Calls the associateRoleToStory method, if the return is true, then it
	 * redirects back to the story editing page, otherwise it displays an error
	 * message to indicate that the role is already associated to the story.
	 * 
	 * @param roleId
	 *            The id of the role that will be associated to the story.
	 * @param storyId
	 *            The id of the user story selected role will be associated.
	 */
	public static void submitAssociateRole(final long[] roleId,
			final long storyId) {
		Security
				.checkAccess(
						"Associate/disassociate application roles to/from a user story",
						storyId, UserStory.class.getName());
		List<ApplicationRole> appRoleList = new LinkedList<ApplicationRole>();
		if (roleId != null) {
			for (int i = 0; i < roleId.length; i++) {
				appRoleList.add((ApplicationRole) ApplicationRole
						.findById(roleId[i]));
			}
		}
		if (!appRoleList.isEmpty()) {
			associateRolesToStory(appRoleList, storyId);
			UserStories.show(storyId);
		} else {
			flash.error("Erorr! You must select a"
					+ " role before clicking submit!");
			showAssociateRole(storyId);
		}

	}

	/**
	 * Associates the Application Roles to the User Story with the given storyId
	 * if it is not already associated with it. Returns true if the roles have
	 * successfully been associated. Returns false if the role is already
	 * associated with the story.
	 * 
	 * @param appRoles
	 *            The list of the roles that will be associated to the story.
	 * @param storyId
	 *            The id of the user story selected role will be associated.
	 * @return boolean - returns true in case of success.
	 */
	public static boolean associateRolesToStory(
			final List<ApplicationRole> appRoles, final long storyId) {
		UserStory story = UserStory.findById(storyId);
		while (!appRoles.isEmpty()) {
			if (!story.appRoles.contains(appRoles.get(0))) {
				ApplicationRole currentRole = appRoles.remove(0);
				story.appRoles.add(currentRole);
				story.save();
				currentRole.userStories.add(story);
				currentRole.save();
			} else {
				appRoles.remove(0);
			}
		}
		return true;
	}

	/**
	 * Calls the delete method. It then redirects to the view of all the
	 * Application Roles in the project.
	 * 
	 * @param roleId
	 *            The id of the role that will be associated to the story.
	 * @param projectId
	 *            The id of the project for which this role is associated.
	 */
	public static void showDelete(final long roleId, final long projectId) {
		Security.checkAccess(
				"Create/edit/delete application roles in my project ",
				projectId, Project.class.getName());
		delete(roleId, projectId);
		showAll(projectId);
	}

	/**
	 * Deletes an Application Role from the Project with the given projectId.
	 * The method also removes all associations of the Application Role with the
	 * User Stories in the Project. It then redirects to the view of all the
	 * Application Roles in the project.
	 * 
	 * @param roleId
	 *            The id of the role that will be associated to the story.
	 * @param projectId
	 *            The id of the project for which this role is associated.
	 * @return boolean - returns true in case of success
	 */
	public static boolean delete(final long roleId, final long projectId) {
		ApplicationRole appRole = ApplicationRole.findById(roleId);
		while (!appRole.userStories.isEmpty()) {
			appRole.userStories.get(0).appRoles.remove(appRole);
			appRole.userStories.get(0).save();
			appRole.userStories.remove(0);
			appRole.save();
		}
		appRole.project.appRoles.remove(appRole);
		appRole.project.save();
		appRole.delete();
		return true;
	}

	/**
	 * Disassociates the selected Application Role with roleId from the given
	 * User Story with storyId.
	 * 
	 * @param roleId
	 *            The id of the role that will be disassociated from the story.
	 * @param storyId
	 *            The id of the story that the role is associated to.
	 * @return - returns boolean whether the disassociation was successful or
	 *         not.
	 */
	public static boolean disassociateRole(final long roleId, final long storyId) {
		ApplicationRole appRoles = ApplicationRole.findById(roleId);
		UserStory story = UserStory.findById(storyId);
		story.appRoles.remove(appRoles);
		story.save();
		appRoles.userStories.remove(story);
		appRoles.save();
		return true;
	}

	/**
	 * Calls the disassociateRole method and redirects to the editing story
	 * page.
	 * 
	 * @param roleId
	 *            The id of the role that will be disassociated from the story.
	 * @param storyId
	 *            The id of the story that the role is associated to.
	 */
	public static void showDisassociateRole(final long roleId,
			final long storyId) {
		Security
				.checkAccess(
						"Associate/disassociate application roles to/from a user story",
						storyId, UserStory.class.getName());
		disassociateRole(roleId, storyId);
		UserStories.show(storyId);
	}
}
